package codecs;

import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;

import javax.media.Buffer;
import javax.media.Codec;
import javax.media.Format;
import javax.media.ResourceUnavailableException;
import javax.media.format.RGBFormat;
import javax.media.format.VideoFormat;
import javax.media.format.YUVFormat;

import model.AnchorPoint;
import model.AnchorPointManager;
import model.Direction;

import org.w3c.dom.Element;

import xml.XMLGenerator;

/**
 * Calculates the difference image and draws it on the screen
 * @author	Daniel Mohr, Daniela Grammlich
 */
public class DifferenceCodec implements Codec {

	/**
	 * Defines the mask for the alpha channel
	 */
	final static int ALPHA_MASK = 0xFF000000;

	/**
	 * The input format
	 */
	private Format inputFormat;
	/**
	 * The output format
	 */
	private Format outputFormat;
	/**
	 * The supported input formats
	 */
	private Format[] inputFormats;
	/**
	 * The supported output formats
	 */
	private Format[] outputFormats;

	/**
	 * Holds the previous frame
	 */
	private int[] previousFrame;
	/**
	 * Holds the previous difference image
	 */
	private int[] previousDiffFrame;
	/**
	 * Holds the actual difference image
	 */
	private int[] diffBuffer;

	/**
	 * The control to access the DifferenceCodec data
	 */
	private DifferenceCodecControl control = new DifferenceCodecControl();
	/**
	 * The control to access the BoderCodec data
	 */
	private BorderCodecControlInterface bcci;
	/**
	 * Holds the anchor points
	 */
	private AnchorPointManager anchorManager;
	/**
	 * Generator to write the data to a xml file
	 */
	private XMLGenerator generator;

	/**
	 * Holds the actual frame
	 */
	private int[] frame = null;
	/**
	 * The output buffer
	 */
	private int[] outBuffer = null;
	/**
	 * Buffer with binary image
	 */
	private int[][] binBuffer;
	
	/**
	 * Border to detect anchor points
	 */
	private static int threshold = 50;
	/**
	 * The width
	 */
	private int width = 0;
	/**
	 * The height
	 */
	private int height = 0;
	/**
	 * The sum of all x values in one grid
	 */
	private int xSum = 0;
	/**
	 * The sum of all y values in one grid
	 */
	private int ySum = 0;
	/**
	 * The number of values
	 */
	private int sumCount = 0;
	
	/**
	 * Holds the previous anchor points
	 */
	private ArrayList<AnchorPoint> previousAnchorPoints;

	/**
	 * Is this the first frame?
	 */
	private boolean first = true;
	/**
	 * Are the anchor points initialized?
	 */
	private boolean initialiseAnchorPoints = true;
	/**
	 * Are previous anchors available?
	 */
	private boolean previousAnchorsAvailable = false;

	/**
	 * Constructor
	 * @param bcci	the control of the BorderCodec
	 */
	public DifferenceCodec(BorderCodecControlInterface bcci)
	{
		inputFormats = new Format[]{
				new RGBFormat(), 
				new YUVFormat()}; 
		outputFormats = new Format[]{
				new RGBFormat(), 
				new YUVFormat()};
		this.bcci = bcci;
		anchorManager = new AnchorPointManager();
		generator = new XMLGenerator();
		control.setXmlGenerator(generator);
		previousAnchorPoints = new ArrayList<AnchorPoint>();	
	}

	@Override
	public Format[] getSupportedInputFormats() 
	{
		return inputFormats;
	}

	@Override
	public Format[] getSupportedOutputFormats(Format input) 
	{
		if (input==null) return(outputFormats);
		if (input instanceof RGBFormat)
			return(new Format[] {   
					input,
					outputFormats[0]
			});
		else return(outputFormats);
	}

	@Override
	public int process(Buffer inputBuffer, Buffer outputBuffer) 
	{
		outputBuffer.copy(inputBuffer);
		RGBFormat format = (RGBFormat)inputBuffer.getFormat();
		Dimension size = format.getSize();
		width = (int) size.getWidth();
		height = (int)size.getHeight();
		
		frame = (int[])inputBuffer.getData();
		
		if(first)
		{
			outBuffer = new int[frame.length];
			binBuffer = new int[width][height];
			
			diffBuffer = new int[frame.length];
			previousFrame = new int[frame.length];
			previousDiffFrame = new int[frame.length];
		}
		
		int outputDataLength = ((VideoFormat) outputFormat).getMaxDataLength();
		outputBuffer.setFormat(outputFormat);
		outputBuffer.setLength(outputDataLength);
		outputBuffer.setFlags(inputBuffer.getFlags());
		outputBuffer.setOffset(outputBuffer.getOffset());
		
		int binWidth = 0,binHeight = 0, sumPixels = 0, gridNumber = 0;

		int rectWidth = bcci.getRectWidth();
		int rectHeight = bcci.getRectHeight();
		int offsetX = bcci.getOffsetX();
		int offsetY = bcci.getOffsetY();
		
		for (int i = 0; i < (width * height); i++)
		{
			int r = ( (int) frame[i]) >> 16 & 0xFF ;
			int g = ( (int) frame[i]) >> 8 & 0xFF;
			int b = ( (int) frame[i]) & 0xFF;
	
			if(first)
			{
				diffBuffer[i] = (ALPHA_MASK | (r << 16) | (g << 8) | (b));
				previousFrame[i] = diffBuffer[i];
				previousDiffFrame[i] = diffBuffer[i];
			}
			else
			{
				if(binWidth > (offsetX) && binWidth < (offsetX + rectWidth)
						&& binHeight > (offsetY) && binHeight < (offsetY + rectHeight)) {
					
					int prevColor = previousFrame[i];								
		
					int prevR = prevColor >> 16 & 0xFF;
					int prevG = prevColor >> 8 & 0xFF;
					int prevB = prevColor & 0xFF; 
		
					int diffR = Math.abs(r - prevR);
					int diffG = Math.abs(g - prevG);
					int diffB = Math.abs(b - prevB);
					
					diffBuffer[i] = (ALPHA_MASK | (diffR << 16) | (diffG << 8) | (diffB));
					previousFrame[i] = (ALPHA_MASK | (r << 16) | (g << 8) | (b));
					previousDiffFrame[i] = diffBuffer[i];
		
					
					outBuffer[i] = (ALPHA_MASK | (diffR << 16) | (diffG << 8) | (diffB));

					if(diffR > threshold | diffG > threshold | diffB > threshold)
					{
						binBuffer[binWidth][binHeight] = 1;
						sumPixels++;

						if(initialiseAnchorPoints && binBuffer[binWidth - 1][binHeight] != 1 
								&& binWidth > (offsetX) && binWidth < (offsetX + rectWidth)
								&& binHeight > (offsetY) && binHeight < (offsetY + rectHeight)){
							
							if((binWidth - offsetX)/16 < 1){
								if((binHeight - offsetY)/16 < 1){
									gridNumber = 1;
								}
								else
								{
									gridNumber = 1 * ((binHeight - offsetY)/16);
								}
							}
							else
							{
								if((binHeight - offsetY)/16 < 1)
								{
									gridNumber = ((binWidth - offsetX)/16) * 1;
								}
								else
								{
									gridNumber = ((binWidth - offsetX)/16) * ((binHeight - offsetY)/16);
								}
							}

							anchorManager.createAnchorPoint(binWidth, binHeight, gridNumber);
							outBuffer[i] = 0xFF0000FF;
						}						
					}
					else
					{
						binBuffer[binWidth][binHeight] = 0;
					}
					
				} else {
					outBuffer[i] = (ALPHA_MASK | (r << 16) | (g << 8) | (b));
					binBuffer[binWidth][binHeight] = outBuffer[i];
				}
				binWidth++;
				if((binWidth == width)){
					binHeight++;
					binWidth = 0;
				}
			}		
		}
		outputBuffer.setData(outBuffer);
		control.setBinary(binBuffer);
		
		if(sumPixels > (0.0005 * (width * height)))
		{
			initialiseAnchorPoints = true;
		}else if(sumPixels < (0.0005 * (width * height)))
		{
			initialiseAnchorPoints = false;
			anchorManager.deleteAnchorPoints();
			previousAnchorsAvailable = false;
			
		}
		
		if(previousAnchorsAvailable){	
			Element el = generator.appendXml();
			computeVectors(el);
			previousAnchorPoints = anchorManager.getCurrentAnchorPointList();
			anchorManager.deleteAnchorPoints();
		}else if(!anchorManager.getAnchorList().isEmpty())
		{
			sortArrayList();
			previousAnchorPoints = anchorManager.getCurrentAnchorPointList();
			previousAnchorsAvailable = true;
		}
		
		first = false;
		return BUFFER_PROCESSED_OK;
	}
	
	/**
	 * Writes the found vectors into a xml file
	 * @param parent
	 */
	public void computeVectors(Element parent){
		sortArrayList();
		for(Iterator<AnchorPoint> iterator = previousAnchorPoints.iterator(); iterator.hasNext();)  {
			AnchorPoint prevPoint = iterator.next();
			
			xSum = 0;
			ySum = 0;
			sumCount = 0;
			
			for(Iterator<AnchorPoint> iteratoaer = anchorManager.getCurrentAnchorPointList().iterator(); iteratoaer.hasNext();){
				AnchorPoint currentPoint = iteratoaer.next();
				if((prevPoint.getGridNumber() == currentPoint.getGridNumber())){
					sumCount++;

					xSum += (prevPoint.getStartXPos() - currentPoint.getStartXPos());
					ySum += (prevPoint.getStartYPos() - currentPoint.getStartYPos());			
				}
			}
			if(sumCount != 0 && ((xSum/sumCount) != 0 && (ySum/sumCount) != 0)){		
				generator.appendXml("x", xSum/sumCount, parent);
				generator.appendXml("y", ySum/sumCount, parent);		
				//FIXME: Irgendwas geht bei der Berechnung schief
				generator.appendXml("direction", getDirection(Math.toDegrees(Math.atan((prevPoint.getStartYPos() - (ySum/sumCount))/(prevPoint.getStartXPos() - (xSum/sumCount))))), parent);
			}
		}
	}
	
	/**
	 * Sorts the anchorpoints
	 */
	public void sortArrayList(){	
		Collections.sort(anchorManager.getCurrentAnchorPointList(), new Comparator<AnchorPoint>(){
			public int compare(AnchorPoint first, AnchorPoint second){
				if(first.getGridNumber() > second.getGridNumber()){
					return 1;
				} 
				if(first.getGridNumber() < second.getGridNumber()){
					return -1;
				}
				return 0;
			}
		});
	}
	
	/**
	 * Calculates the direction of the movement
	 * @param angle	of the movement
	 * @return	the direction, else 0
	 */
	public int getDirection(double angle){
		if(angle < 0){
			angle += 360;
		}
		if(angle > 360){
			angle = angle % 360;
		}
		if(angle > 0 && angle <= 22.5 && angle > 337.5 && angle <= 360){
			return Direction.up;
		}else if(angle > 22.5 && angle <= 67.5){
			return Direction.right_up;
		}else if(angle > 67.5 && angle <= 112.5){
			return Direction.right;
		}else if(angle > 112.5 && angle <= 157.5){
			return Direction.right_down;
		}else if(angle > 157.5 && angle <= 202.5){
			return Direction.down;
		}else if(angle > 202.5 && angle <= 247.5){
			return Direction.left_down;
		}else if(angle > 247.5 && angle <= 292.5){
			return Direction.left;
		}else if(angle > 292.5 && angle <= 337.5){
			return Direction.left_up;
		}
		return 0;
	}

	@Override
	public Format setInputFormat(Format input) {
		if (input instanceof RGBFormat) inputFormat = (RGBFormat)input;
		else inputFormat = (RGBFormat)inputFormats[0];
		return(inputFormat);
	}

	@Override
	public Format setOutputFormat(Format output) {
		if (output instanceof RGBFormat) outputFormat = (RGBFormat)output;
		else outputFormat = (RGBFormat)outputFormats[0];
		return(outputFormat);
	}

	@Override
	public String getName() 
	{
		return new String("DifferenceCodec");
	}

	@Override
	public void open() throws ResourceUnavailableException 
	{
	}

	@Override
	public void reset() 
	{
	}

	@Override
	public Object getControl(String control) 
	{
		return this.control;
	}

	@Override
	public Object[] getControls() 
	{
		Object[] controls = {control};
		return controls;
	}

	@Override
	public void close() 
	{		
	}
}

